package com.pingxx.demo.integration;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.locks.Lock;

@Slf4j
@Aspect
@Component
public class DistributedLockAspect {

    private RedisLockRegistry redisLockRegistry;

    @Pointcut("@annotation(DistributedLock)")
    public void distributedLockAspect() {
    }

    @Around(value = "distributedLockAspect()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Class targetClass = pjp.getTarget().getClass();
        String methodName = pjp.getSignature().getName();
        Class[] parameterTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();
        Method method = targetClass.getMethod(methodName, parameterTypes);
        Object[] arguments = pjp.getArgs();
        DistributedLock annotation = method.getAnnotation(DistributedLock.class);
        String lockKey = arguments.length > 0 && !StringUtils.isEmpty(annotation.expression()) ? getLockKey(annotation, arguments) : targetClass.getName() + ":" + methodName;
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean result = false;
        try {
            result = lock.tryLock(annotation.expire(), annotation.timeUnit());
            if (result) {
                return pjp.proceed();
            }
        } catch (InterruptedException e) {
            //
            log.error("lock error", e);
        } finally {
            if (result) {
                lock.unlock();
            }
        }
        throw new LockException("lock error");
    }

    private String getLockKey(DistributedLock annotation, Object[] arguments) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        context.setVariable("arg", arguments);
        return parser.parseExpression(annotation.expression()).getValue(context, String.class);
    }

    @Autowired
    public void setRedisLockRegistry(RedisLockRegistry redisLockRegistry) {
        this.redisLockRegistry = redisLockRegistry;
    }
}
